package cn.stalk.wf.rest;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.camunda.bpm.engine.ActivityTypes;
import org.camunda.bpm.engine.FormService;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.form.TaskFormData;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.history.HistoricDetail;
import org.camunda.bpm.engine.history.HistoricDetailQuery;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricProcessInstanceQuery;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstanceQuery;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.history.HistoricVariableInstanceQuery;
import org.camunda.bpm.engine.impl.persistence.entity.HistoricDetailVariableInstanceUpdateEntity;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Comment;
import org.camunda.bpm.engine.task.DelegationState;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.engine.variable.VariableMap;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import cn.stalk.wf.common.Constant;
import cn.stalk.wf.common.Response;
import cn.stalk.wf.common.ResponsePage;
import cn.stalk.wf.component.ProcessDefinitionCache;
import cn.stalk.wf.service.FlowService;
import cn.stalk.wf.vo.FlowInst;
import cn.stalk.wf.vo.FlowTask;
import cn.stalk.wf.vo.KeyValue;
import cn.stalk.wf.vo.KeyValueExt;
import cn.stalk.wf.vo.TaskComment;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("flow")
public class FlowController {

    @Autowired
    private TaskService taskService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private FormService formService;
    @Autowired
    private IdentityService identityService;

    @Autowired
    private FlowService flowService;
    @Autowired
    private ProcessDefinitionCache processDefinitionCache;

    // 查询正在运行中的任务及其相关信息
    private ResponsePage queryTask(TaskQuery taskQuery, int startPage, int limit) {
        long total = taskQuery.count();
        if (total == 0) {
            return new ResponsePage(0, new ArrayList<>());
        }
        List<Task> tasks = taskQuery.orderByTaskCreateTime().desc().listPage((startPage - 1) * limit, limit);

        List<FlowTask> list = tasks.stream().map(e -> {
            String flowKey = processDefinitionCache.findById(e.getProcessDefinitionId()).getKey();
            Collection<String> nextKey = new ArrayList<>();

            TaskFormData data = formService.getTaskFormData(e.getId());
            if (null != data) {
                nextKey = data.getFormFields().stream().map(f -> f.getId()).collect(Collectors.toList());
            }

            Map<String, Object> variables = runtimeService.getVariables(e.getExecutionId());
            return new FlowTask().setId(e.getId()).setProcessInstanceId(e.getProcessInstanceId()).setTheme((String) variables.get(Constant.Flow.KEY_THEME)) //
                    .setFollowUpDate(e.getFollowUpDate()).setStartTime(e.getCreateTime()).setFlowName(e.getProcessDefinitionId()) //
                    .setTaskName(e.getName()).setAssignee(e.getAssignee()).setOwner(e.getOwner()) //
                    .setFlowKey(flowKey).setNextKey(nextKey);
        }).collect(Collectors.toList());

        return new ResponsePage(total, list);
    }

    // 所有流程类型
    @GetMapping(value = "/list")
    public Response list() {
        Collection<ProcessDefinition> list = processDefinitionCache.allLatest();
        List<KeyValueExt> result = list.stream().map(e -> {
            KeyValueExt obj = new KeyValueExt();
            obj.setKey(e.getKey()).setValue(e.getName());

            Map<String, Object> temp = new HashMap<>();
            VariableMap vm = formService.getStartFormVariables(e.getId());
            if (null != vm) {
                temp.putAll(vm);
            }
            obj.setData(temp);
            return obj;
        }).collect(Collectors.toList());

        return Response.successData(result);
    }

    // 可领取的任务
    @GetMapping(value = "/assignable")
    public Response assignable(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.taskCandidateUser(user);
        taskQuery.active();

        return queryTask(taskQuery, startPage, limit);
    }

    // 我的待办任务
    @GetMapping(value = "/mytask")
    public Response mytask(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.taskAssignee(user);
        taskQuery.active();

        return queryTask(taskQuery, startPage, limit);
    }

    // 我的已办任务
    @GetMapping(value = "/mydone")
    public Response mydone(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        HistoricTaskInstanceQuery hisTaskQuery = historyService.createHistoricTaskInstanceQuery();
        hisTaskQuery.taskAssignee(user);
        hisTaskQuery.finished();

        long total = hisTaskQuery.count();
        if (total == 0) {
            return new ResponsePage(0, new ArrayList<>());
        }
        List<HistoricTaskInstance> tasks = hisTaskQuery.orderByHistoricTaskInstanceEndTime().desc().listPage((startPage - 1) * limit, limit);

        List<FlowTask> list = tasks.stream().map(e -> {
            HistoricVariableInstanceQuery hisVariableQuery = historyService.createHistoricVariableInstanceQuery();
            Map<String, Object> variablesMap = new HashMap<>();
            List<HistoricVariableInstance> variablesList = hisVariableQuery.taskIdIn(e.getId()).list();
            variablesList.forEach(v -> variablesMap.put(v.getName(), v.getValue()));

            return new FlowTask().setId(e.getId()).setTaskName(e.getName()).setProcessInstanceId(e.getProcessInstanceId()).setStartTime(e.getStartTime()).setEndTime(e.getEndTime())
                    .setTheme((String) variablesMap.get(Constant.Flow.KEY_THEME)).setAssignee(e.getAssignee());
        }).collect(Collectors.toList());

        return new ResponsePage(total, list);
    }

    // 运行中的流程admin
    @GetMapping(value = "/running")
    public Response running(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            // 此接口只给admin用户使用
            return new ResponsePage(0, new ArrayList<>());
        }
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.active();

        return queryTask(taskQuery, startPage, limit);
    }

    // 暂停的任务admin
    @GetMapping(value = "/suspended")
    public Response suspended(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            // 此接口只给admin用户使用
            return new ResponsePage(0, new ArrayList<>());
        }
        TaskQuery taskQuery = taskService.createTaskQuery();
        taskQuery.suspended();

        return queryTask(taskQuery, startPage, limit);
    }

    // 完成的流程admin
    @GetMapping(value = "/finished")
    public Response finished(@RequestParam(value = "startPage", defaultValue = "1") int startPage, @RequestParam(value = "limit", defaultValue = "10") int limit,
            @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            // 此接口只给admin用户使用
            return new ResponsePage(0, new ArrayList<>());
        }

        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
        query.finished();

        long total = query.count();
        if (total == 0) {
            return new ResponsePage(0, new ArrayList<>());
        }
        List<HistoricProcessInstance> tasks = query.orderByProcessInstanceEndTime().desc().listPage((startPage - 1) * limit, limit);
        List<FlowInst> list = tasks.stream().map(e -> {
            return new FlowInst().setId(e.getId()).setProcessDefinitionId(e.getProcessDefinitionId()).setProcessDefinitionName(e.getProcessDefinitionName())
                    .setStartTime(e.getStartTime()).setEndTime(e.getEndTime()).setRemovalTime(e.getRemovalTime()).setDeleteReason(e.getDeleteReason()).setState(e.getState())
                    .setDurationInMillis(e.getDurationInMillis()).setStartUserId(e.getStartUserId());
        }).collect(Collectors.toList());

        return new ResponsePage(total, list);
    }

    // 查看流程历史
    @GetMapping(value = "/history/{processInstanceId}")
    public Response history(@PathVariable("processInstanceId") String processInstanceId) {
        List<HistoricTaskInstance> resultList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).finished()
                .orderByHistoricTaskInstanceEndTime().desc().list();
        List<FlowTask> list = resultList.stream().map(e -> {
            List<Comment> tempList = taskService.getTaskComments(e.getId());
            List<TaskComment> comments = tempList.stream().map(c -> {
                TaskComment tc = new TaskComment();
                BeanUtils.copyProperties(c, tc);
                return tc;
            }).collect(Collectors.toList());

            FlowTask task = new FlowTask();
            task.setId(e.getId()).setTaskName(e.getName());
            task.setActivityInstanceId(e.getActivityInstanceId());
            task.setExecutionId(e.getExecutionId());
            task.setAssignee(e.getAssignee());
            task.setOwner(e.getOwner());
            task.setStartTime(e.getStartTime());
            task.setEndTime(e.getEndTime());
            task.setDurationInMillis(e.getDurationInMillis());
            task.setComments(comments);
            return task;
        }).collect(Collectors.toList());

        return Response.successData(list);
    }

    // 查看流程变量详情
    @GetMapping(value = "/processVariables/{executionId}")
    public Response processVariables(@PathVariable("executionId") String executionId) {
        HistoricVariableInstanceQuery hisQuery = historyService.createHistoricVariableInstanceQuery();
        List<HistoricVariableInstance> list = hisQuery.executionIdIn(executionId).list();

        Map<String, Object> variables = new HashMap<>();
        list.forEach(e -> variables.put(e.getName(), e.getValue()));

        return Response.successData(variables);
    }

    // 查看任务变量详情
    @GetMapping(value = "/taskVariables/{activityInstanceId}")
    public Response taskVariables(@PathVariable("activityInstanceId") String activityInstanceId) {
        HistoricDetailQuery hisDetailQuery = historyService.createHistoricDetailQuery();
        List<HistoricDetail> resultList = hisDetailQuery.activityInstanceId(activityInstanceId).list();

        List<KeyValue> list = resultList.stream().filter(e -> e instanceof HistoricDetailVariableInstanceUpdateEntity).map(e -> {
            HistoricDetailVariableInstanceUpdateEntity b = (HistoricDetailVariableInstanceUpdateEntity) e;
            return new KeyValue(b.getName(), null == b.getValue() ? null : b.getValue().toString());
        }).collect(Collectors.toList());
        return Response.successData(list);
    }

    // 获取流程所有节点信息
    @GetMapping(value = "/nodes")
    public Response nodes(@RequestParam(value = "processDefinitionKey", required = false) String processDefinitionKey,
            @RequestParam(value = "processDefinitionId", required = false) String processDefinitionId) {
        if (StringUtils.isBlank(processDefinitionId)) {
            ProcessDefinition definition = processDefinitionCache.findByKey(processDefinitionKey);
            if (null == definition) {
                return Response.failedMessage("未找到流程定义");
            }
            processDefinitionId = definition.getId();
        }
        Collection<UserTask> userTasks = flowService.parseUserTasks(processDefinitionId);
        List<KeyValue> list = userTasks.stream().map(e -> new KeyValue(e.getId(), e.getName())).collect(Collectors.toList());

        return Response.successData(list);
    }

    // 获取任务候选人
    @GetMapping(value = "/candidate/{taskId}")
    public Response candidate(@PathVariable("taskId") String taskId) {
        Set<String> userList = flowService.selectTaskCandidate(taskId);
        return Response.successData(userList);
    }

    // 启动
    @PostMapping(value = "/start/{flowKey}")
    public Response start(@PathVariable("flowKey") String flowKey, @RequestBody Map<String, Object> variables, @RequestHeader("username") String user) {
        if (StringUtils.isBlank(user)) {
            return Response.failedMessage("未登录，无法执行");
        }
        identityService.setAuthenticatedUserId(user);
        ProcessInstance instance = runtimeService.startProcessInstanceByKey(flowKey, variables);
        if (instance == null) {
            return Response.failedMessage("流程启动失败");
        }
        Task task = taskService.createTaskQuery().processInstanceId(instance.getId()).singleResult();
        if (task == null) {
            return Response.failedMessage("未找到任务信息，执行失败");
        }
        log.info("任务已启动：" + instance.getId());

        return Response.successData(instance.getId());
    }

    // 终止
    @PostMapping(value = "/terminate/{processInstanceId}")
    public Response terminate(@PathVariable("processInstanceId") String processInstanceId, @RequestParam("reason") String reason, @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            return Response.failedMessage("非管理员，无法终止流程");
        }
        log.info("reason=={}", reason);
        identityService.setAuthenticatedUserId(user);
        runtimeService.deleteProcessInstance(processInstanceId, reason);
        log.info("流程已终止：" + processInstanceId);

        return Response.successData(processInstanceId);
    }

    // 挂起
    @PostMapping(value = "/pause/{processInstanceId}")
    public Response pause(@PathVariable("processInstanceId") String processInstanceId, @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            return Response.failedMessage("非管理员，无法挂起流程");
        }
        identityService.setAuthenticatedUserId(user);
        runtimeService.suspendProcessInstanceById(processInstanceId);
        log.info("流程已挂起：" + processInstanceId);

        return Response.successData(processInstanceId);
    }

    // 唤醒
    @PostMapping(value = "/resume/{processInstanceId}")
    public Response resume(@PathVariable("processInstanceId") String processInstanceId, @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            return Response.failedMessage("非管理员，无法唤醒流程");
        }
        identityService.setAuthenticatedUserId(user);
        runtimeService.activateProcessInstanceById(processInstanceId);
        log.info("流程已唤醒：" + processInstanceId);

        return Response.successData(processInstanceId);
    }

    // 完成（完成委托）
    @SuppressWarnings("unchecked")
    @PostMapping(value = "/complete/{processInstanceId}")
    public Response complete(@PathVariable("processInstanceId") String processInstanceId, @RequestBody Map<String, Object> body, @RequestHeader("username") String user) {
        String comment = (String) body.get("comment");
        Map<String, Object> variables = (Map<String, Object>) body.get("variables");
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        if (task == null) {
            return Response.failedMessage("未找到任务信息，执行失败");
        }
        if (null == task.getAssignee()) {
            return Response.failedMessage("任务还未认领，无法执行");
        }
        if (!user.equals(task.getAssignee())) {
            return Response.failedMessage("任务已被【" + task.getAssignee() + "】认领，无法执行");
        }

        identityService.setAuthenticatedUserId(user);
        if (StringUtils.isNotBlank(comment)) {
            taskService.createComment(task.getId(), processInstanceId, comment);
        }

        // 完成的任务可能是别人委托给他的
        if (null == task.getDelegationState() || DelegationState.RESOLVED.equals(task.getDelegationState())) {
            taskService.complete(task.getId(), variables);
            log.info("任务{}已执行完成", task.getId());
        } else {
            taskService.resolveTask(task.getId(), variables);
            log.info("已解决委托任务{}", task.getId());
        }

        return Response.successData(task.getId());
    }

    // 认领
    @PostMapping(value = "/claim/{taskId}")
    public Response claim(@PathVariable("taskId") String taskId, @RequestHeader("username") String user) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return Response.failedMessage("未找到任务信息，执行失败");
        }
        if (null != task.getAssignee()) {
            return Response.failedMessage("任务已被【" + task.getAssignee() + "】认领");
        }

        identityService.setAuthenticatedUserId(user);
        taskService.claim(task.getId(), user);
        log.info("任务{}已认领", task.getId());

        return Response.successData(task.getId());
    }

    // 指派
    @PostMapping(value = "/assign/{taskId}")
    public Response assign(@PathVariable("taskId") String taskId, @RequestParam("transactor") String transactor, @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            return Response.failedMessage("非管理员，无法指派办理人");
        }
        identityService.setAuthenticatedUserId(user);

        // 此处不可用claim方法，claim会检查任务是否已有办理人，有办理人时无法再指派
        taskService.setAssignee(taskId, transactor);
        log.info("任务{}已指派到{}", taskId, transactor);

        return Response.successData(taskId);
    }

    // 委派
    @PostMapping(value = "/delegate/{taskId}")
    public Response delegate(@PathVariable("taskId") String taskId, @RequestParam("target") String target, @RequestHeader("username") String user) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return Response.failedMessage("未找到任务信息，执行失败");
        }
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            return Response.failedMessage("当前任务正在委托中，无法再次委托");
        }
        if (!user.equals(task.getAssignee())) {
            return Response.failedMessage("非当前任务办理人，无法委托");
        }
        if (task.getAssignee().equals(target)) {
            return Response.failedMessage("无法将任务委托给自己");
        }

        identityService.setAuthenticatedUserId(user);
        taskService.delegateTask(task.getId(), target);
        log.info("任务{}已委托到{}", task.getId(), target);

        return Response.successData(task.getId());
    }

    // 退回
    @PostMapping(value = "/back/{taskId}")
    public Response back(@PathVariable("taskId") String taskId, @RequestParam(value = "comment", required = false) String comment, @RequestHeader("username") String user) {
        if (!Constant.Manager.USERNAME.equals(user)) {
            return Response.failedMessage("非管理员，无法退回任务");
        }
        HistoricTaskInstance target = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
        if (target == null) {
            return Response.failedMessage("未找到任务信息，执行失败");
        }
        Task current = taskService.createTaskQuery().processInstanceId(target.getProcessInstanceId()).singleResult();

        List<HistoricActivityInstance> hisActList = historyService.createHistoricActivityInstanceQuery().processInstanceId(target.getProcessInstanceId())
                .activityType(ActivityTypes.TASK_USER_TASK).list();
        HistoricActivityInstance targetAct = null, currentAct = null;
        for (HistoricActivityInstance temp : hisActList) {
            if (null == targetAct && temp.getTaskId().equals(target.getId())) {
                targetAct = temp;
            } else if (null == currentAct && temp.getTaskId().equals(current.getId())) {
                currentAct = temp;
            }
            if (null != targetAct && null != currentAct) {
                break;
            }
        }
        if (null == targetAct || null == currentAct) {
            return Response.failedMessage("未找到目标节点活动信息，执行失败");
        }

        List<HistoricVariableInstance> variablesList = historyService.createHistoricVariableInstanceQuery().taskIdIn(taskId).list();
        Map<String, Object> taskVariable = new HashMap<>();
        variablesList.forEach(e -> taskVariable.put(e.getName(), e.getValue()));

        identityService.setAuthenticatedUserId(user);
        runtimeService.createProcessInstanceModification(current.getProcessInstanceId()) //
                .setAnnotation(StringUtils.defaultString(comment, "管理员退回"))//
                .cancelActivityInstance(currentAct.getId())// 关闭相关任务
                .startBeforeActivity(targetAct.getActivityId())// 启动目标活动节点
                .setVariables(taskVariable)// 流程的可变参数赋值
                .execute();

        return Response.successMessage("退回成功");
    }

}
